/**
 * @file useFieldArray
 * @author  fanxl
 */
import {reactive, toRefs, unref, watch, computed} from 'vue';
import type {ComputedRef} from 'vue';
import {cloneDeep, isUndefined, isFunction, get, escapeRegExp, pickBy, mapKeys, replace, startsWith} from 'lodash-es';
import {convertLeafValue, createRandomChars} from './utils';
import {useField} from './useField';
import {useFormInject} from './useForm';
import type {
    NestedObjectTypeValue,
    FieldRegistryType,
    FieldInputProps,
    FieldHelperProps,
    ValuesState,
    ErrorState
} from './types';
import type {ListLikeField, UnionSpecItem} from '../interface';
import {useRegistField} from './useRegistField';

export interface ArrayHelpers {
    push: (value: any) => void;
    remove: (index: number) => any;
    swap: (from: number, to: number) => void;
}

export interface UseFieldArrayProps {
    field: FieldInputProps;
    fieldHelper: FieldHelperProps;
    arrayHelpers: ArrayHelpers;
}

export const swap = (array: Array<any>, indexA: number, indexB: number) => {
    const copy = cloneDeep(array);
    const a = copy[indexA];
    copy[indexA] = copy[indexB];
    copy[indexB] = a;
    return copy;
};

type updateFn<T> = (value: Array<T>) => Array<T>;
type AlterFieldsFn = () => void;

export const getRecordAfterSwap = <T extends object>(fieldRegistry: T, prefix: string, from: number, to: number) => {
    const fromPrefix = `${prefix}.[${from}]`;
    const toPrefix = `${prefix}.[${to}]`;
    const handler = (key: string) => {
        if (startsWith(key, fromPrefix)) {
            return replace(key, toPrefix, fromPrefix);
        } else if (startsWith(key, toPrefix)) {
            return replace(key, fromPrefix, toPrefix);
        }
        return key;
    };
    return mapKeys(fieldRegistry as unknown as any, handler) as unknown as T;
};

/**
 * getRecordAfterRemove
 * @param fieldRegistry
 * @param prefix
 * @param index
 * @returns {FieldRegistryType}
 */
export const getRecordAfterRemove = <T extends object>(fieldRegistry: T, prefix: string, index: number) => {
    const orderRemove = (key: string) => {
        if (startsWith(key, prefix)) {
            const reg = new RegExp('^' + escapeRegExp(prefix) + '.\\[(\\d*)\\]');
            const matchResult = reg.exec(key);
            if (matchResult) {
                const [currentPrefix, currentIndex] = matchResult;
                if (Number(currentIndex) > index) {
                    const targetPrefix = `${prefix}.[${Number(currentIndex) - 1}]`;
                    return replace(key, targetPrefix, currentPrefix);
                }
            }
        }
        return key;
    };
    // const handler = compose(
    //     mapKeys(orderRemove),
    //     pickBy((value: any, key: string) => !startsWith(`${prefix}.[${index}]`, key))
    // );
    // return handler(fieldRegistry);

    return mapKeys(
        pickBy(fieldRegistry, (value: any, key: string) => !startsWith(key, `${prefix}.[${index}]`)),
        orderRemove
    );
};

export const useFieldArray = (fieldConfig: ListLikeField | ComputedRef<ListLikeField>): UseFieldArrayProps => {
    const {field, helper} = useField(fieldConfig);
    const form = useFormInject();
    const {touched, fieldRegistry, listChars} = toRefs(form);
    const chars = computed(() => field.chars);
    let process = '';

    const updateArrayField = (
        fn: updateFn<any>,
        alterTouched: boolean | updateFn<NestedObjectTypeValue<boolean>>,
        alterChars: boolean | updateFn<NestedObjectTypeValue<string>>,
        alterFields: false | AlterFieldsFn
    ) => {
        const updateTouched = typeof alterTouched === 'function' ? alterTouched : fn;
        const updateChar = typeof alterChars === 'function' ? alterChars : fn;

        const prevValues = cloneDeep(field.value) as Array<ValuesState>;
        const fieldValue = fn(prevValues);

        const listFieldTouched = get(unref(touched), field.fieldName);
        if (listFieldTouched) {
            const prevTouched = cloneDeep(listFieldTouched) as Array<NestedObjectTypeValue<boolean>>;
            if (alterTouched) {
                const fieldTouched = updateTouched(prevTouched);
                helper.setTouched(fieldTouched, false);
            }
        }

        if (alterChars) {
            const newChars = updateChar(unref(field.chars)) as Array<string>;
            form.setListChars(field.fieldName, newChars);
        }

        helper.setValue(fieldValue, false, false);
        alterFields && alterFields();
        form.validateForm();
    };

    const registListField = (index: number) => {
        const spec = unref(fieldConfig).spec;
        const fakeSpecs = spec.items
            ? ([
                  {
                      name: `[${index}]`,
                      displayName: spec.displayName,
                      dataType: 'OBJECT',
                      display: 'toggle-block',
                      items: spec.items
                  }
              ] as UnionSpecItem[])
            : ([{...spec.element, name: `[${index}]`}] as UnionSpecItem[]);
        useRegistField(fakeSpecs, form, field.fieldName);
    };

    const unregistListField = (index: number) => {
        // 当前index数据的删除
        const targetFields = Object.keys(form.fieldRegistry).filter((key) =>
            key.startsWith(`${field.fieldName}.[${index}]`)
        );
        targetFields.forEach((fieldName: string) => form.unregisterField(fieldName));

        // 其他兄弟节点数据的变更
        const newFieldRegistry = getRecordAfterRemove<FieldRegistryType>(unref(fieldRegistry), field.fieldName, index);
        form.setBatchFieldsRegistry(newFieldRegistry);
        const newListChars = getRecordAfterRemove<ErrorState>(unref(listChars), field.fieldName, index);
        form.setBatchListChars(newListChars);
    };

    const arrayHelpers = {
        /**
         * 在队列尾部插入一个值
         */
        push(value: any) {
            updateArrayField(
                (array: Array<any>) => [...cloneDeep(array), cloneDeep(value)],
                (array: Array<NestedObjectTypeValue<boolean>>) => [...array, convertLeafValue(value)],
                (arr: Array<NestedObjectTypeValue<string>>) => [...arr, createRandomChars()],
                () => {
                    const idx = form.getFieldValue(field.fieldName).length - 1;
                    process = 'push';
                    registListField(idx);
                }
            );
        },

        /**
         * 移除任意一个位置的元素
         */
        remove(index: number) {
            let result: any;
            updateArrayField(
                (array?: any[]) => {
                    const copy = array ? cloneDeep(array) : [];
                    if (isUndefined(result)) {
                        result = copy[index];
                    }
                    if (isFunction(copy.splice)) {
                        copy.splice(index, 1);
                    }
                    return copy;
                },
                true,
                true,
                () => {
                    process = 'remove';
                    unregistListField(index);
                }
            );

            return result;
        },

        /**
         * 交换from 和to位置
         */
        swap(from: number, to: number) {
            updateArrayField(
                (array: any[]) => swap(array, from, to),
                true,
                true,
                () => {
                    process = 'swap';
                    const newFieldRegistry = getRecordAfterSwap<FieldRegistryType>(
                        unref(fieldRegistry),
                        field.fieldName,
                        from,
                        to
                    );
                    form.setBatchFieldsRegistry(newFieldRegistry);
                    const newListChars = getRecordAfterSwap<ErrorState>(unref(listChars), field.fieldName, from, to);
                    form.setBatchListChars(newListChars);
                }
            );
        }
    };

    /**
     * chars变化四种场景：
     * 1. Add list item
     * 2. Remove list item
     * 3. Swap list item
     * 4. External setFieldValue
     */
    watch(chars, (curr, prev) => {
        if (curr !== prev) {
            if (process) {
                /**
                 * when process is not empty, it is Add or Remove or Swap
                 */
                process = '';
                return;
            }
            /**
             * Special handling, External setFieldValue
             */
            prev.forEach((char: string, index: number) => {
                const targetFields = Object.keys(form.fieldRegistry).filter((key) =>
                    key.startsWith(`${field.fieldName}.[${index}]`)
                );
                targetFields.forEach((fieldName: string) => form.unregisterField(fieldName));
            });

            curr.forEach((char: string, index: number) => {
                registListField(index);
            });
        }
    });
    return reactive({
        field,
        fieldHelper: helper,
        arrayHelpers
    });
};
